#include <TGS/GeoMath2.0/Envelope.h>
#include <limits>
#include <cmath>

namespace TGS {
namespace GeoMath {

Envelope::Envelope(void)
	: _min( std::numeric_limits<double>::max(),  std::numeric_limits<double>::max())
	, _max(-std::numeric_limits<double>::max(), -std::numeric_limits<double>::max())
{
}

Envelope::Envelope(const Point& min, const Point& max)
	: _min(min)
	, _max(max)
{
	 
}

Envelope::Envelope(double minX, double minY, double maxX, double maxY)
	: _min(minX, minY)
	, _max(maxX, maxY)
{
	  
}

Envelope::~Envelope(void)
{
}

//////////////////////////////////////////////////////////////////////////

const Point& Envelope::getMin(void) const
{
	return _min;
}

 const Point& Envelope::getMax(void) const
{
	return _max;
}

inline double Envelope::getMinX(void) const
{
	return _min.getX();
}

inline double Envelope::getMinY(void) const
{
	return _min.getY();
}

inline double Envelope::getMaxX(void) const
{
	return _max.getX();
}

inline double Envelope::getMaxY(void) const
{
	return _max.getY();
}

inline void Envelope::setMin(const Point& min)
{
	_min = min;
}

inline void Envelope::setMax(const Point& max)
{
	_max = max;
}

inline void Envelope::setMin(double minX, double minY)
{
	_min = Point(minX, minY);
}

inline void Envelope::setMax(double maxX, double maxY)
{
	_max = Point(maxX, maxY);
}

inline void Envelope::setMinX(double value)
{
	_min.setX(value);
}

inline void Envelope::setMinY(double value)
{
	_min.setY(value);
}

inline void Envelope::setMaxX(double value)
{
	_max.setX(value);
}

inline void Envelope::setMaxY(double value)
{
	_max.setY(value);
}

inline double Envelope::getWidth(void) const
{
	return _max.getX() - _min.getX();
}

inline double Envelope::getHeight(void) const
{
	return _max.getY() - _min.getY();
}

inline double Envelope::getAera(void) const
{
	return getWidth() * getHeight();
}

//////////////////////////////////////////////////////////////////////////

inline void Envelope::expandToInclude(const Point& point)
{
	const int NoOpeartion = NULL;
	
	point.getX() < _min.getX() ? _min.setX(point.getX()) : NoOpeartion;
	point.getY() < _min.getY() ? _min.setY(point.getY()) : NoOpeartion;

	point.getX() > _max.getX() ? _max.setX(point.getX()) : NoOpeartion;
	point.getY() > _max.getY() ? _max.setY(point.getY()) : NoOpeartion;
}

inline void Envelope::expandToInclude(const Envelope& envelop)
{
	expandToInclude(envelop.getMinX(), envelop.getMinY());
	expandToInclude(envelop.getMaxX(), envelop.getMaxY());
}

inline void Envelope::expandToInclude(double pointX, double pointY)
{
	Point p(pointX, pointY);
	expandToInclude(p);
}

inline void Envelope::expandBy(double deltaX, double deltaY)
{
	_min.setX(_min.getX() - deltaX);
	_min.setY(_min.getY() - deltaY);
	_max.setX(_max.getX() + deltaX);
	_max.setY(_max.getY() + deltaY);
}

inline void Envelope::expandBy(double distance)
{
	expandBy(distance, distance);
}

inline bool Envelope::contain(const Envelope& other) const
{
	return
		_min.getX() <= other.getMinX() &&
		_min.getY() <= other.getMinY() &&
		_max.getX() >= other.getMaxX() &&
		_max.getY() >= other.getMaxY();
}

inline bool Envelope::contain(const Point& point) const
{
	return 
		point.getX() >= _min.getX() &&
		point.getX() <= _max.getX() && 
		point.getY() >= _min.getY() &&
		point.getY() <= _max.getY();
}

inline bool Envelope::contain(double x, double y) const
{
	Point p(x, y);
	return contain(p);
}

inline bool Envelope::intersect(const Envelope& other) const	
{
	if(_max.getX() < other.getMinX()) return false;
	if(_min.getY() > other.getMaxY()) return false;
	if(_min.getX() > other.getMaxX()) return false;
	if(_max.getY() < other.getMinY()) return false;

	return true;
}

bool Envelope::intersect( const Envelope& other, Envelope& intersection ) const
{
	intersection = Envelope();
	if(!this->intersect(other)) return false;

	double minX = this->getMinX() > other.getMinX() ? this->getMinX() : other.getMinX();
	double minY = this->getMinY() > other.getMinY() ? this->getMinY() : other.getMinY();
	double maxX = this->getMaxX() < other.getMaxX() ? this->getMaxX() : other.getMaxX();
	double maxY = this->getMaxY() < other.getMaxY() ? this->getMaxY() : other.getMaxY();

	intersection.expandToInclude(minX, minY);
	intersection.expandToInclude(maxX, maxY);

	return true;
}

inline void Envelope::normalize()
{
	if(_min.getX() > _max.getX())
	{
		double maxX = _min.getX();
		setMinX(_max.getX());
		setMaxX(maxX);
	}

	if(_min.getY() > _max.getY())
	{
		double maxY = _min.getY();
		setMinY(_max.getY());
		setMaxY(maxY);
	}
}


int Envelope::outcode(const Point& point) const
{
	int out = 0;
	if (getWidth() <= 0) 
	{
		out |= OutCode_Left | OutCode_Right;
	}
	else if (point.getX() < getMinX()) 
	{
		out |= OutCode_Left;
	} else if (point.getX() > getMaxX())
	{
		out |= OutCode_Right;
	}

	if (getHeight() <= 0)
	{
		out |= OutCode_Top | OutCode_Bottom;
	} else if (point.getY() < getMinY()) 
	{
		out |= OutCode_Bottom;
	} else if (point.getY() > getMaxY()) 
	{
		out |= OutCode_Top;
	}
	 
	return out;

}

//////////////////////////////////////////////////////////////////////////

inline bool Envelope::Intersection(const Envelope& left, const Envelope& right)
{
	return left.intersect(right);
}

inline bool Envelope::Equal(const Envelope& left, const Envelope& right)
{
	return (left.getMin() == right.getMin()) && (left.getMax() == right.getMax());
}

inline bool Envelope::isEmpty() const
{
	return getMaxX() <= getMinX() || getMaxY() <= getMinY();
}

Point Envelope::getCenter() const
{
	return Point((getMinX() + getMaxX()) / 2.0, (getMinY() + getMaxY()) / 2.0);
}

void Envelope::scale( double scaleFactor, const Point * centerPoint )
{
	// scale from the center
	double centerX, centerY;
	if (nullptr != centerPoint)
	{
		centerX = centerPoint->getX();
		centerY = centerPoint->getY();
	}
	else
	{
		centerX = getMinX() + getWidth() / 2;
		centerY = getMinY() + getHeight() / 2;
	}
	double newWidth = getWidth() * scaleFactor;
	double newHeight = getHeight() * scaleFactor;
	setMinX(centerX - newWidth / 2.0);
	setMaxX(centerX + newWidth / 2.0);
	setMinY(centerY - newHeight / 2.0);
	setMaxY(centerY + newHeight / 2.0);
}

void Envelope::expand( double scaleFactor, const Point* centerPoint /*= nullptr */ )
{
	// scale from the center
	double centerX, centerY;
	if (nullptr != centerPoint)
	{
		centerX = centerPoint->getX();
		centerY = centerPoint->getY();
	}
	else
	{
		centerX = getMinX() + getWidth() / 2;
		centerY = getMinY() + getHeight() / 2;
	}
	double newWidth = getWidth() * scaleFactor;
	double newHeight = getHeight() * scaleFactor;
	setMinX(centerX - newWidth);
	setMaxX(centerX + newWidth);
	setMinY(centerY - newHeight);
	setMaxY(centerY + newHeight);
}

//////////////////////////////////////////////////////////////////////////


}//namespace Nmo
}//namespace Geometry


